const puppeteer = require('puppeteer');
const fs = require('fs');
const helper = require('../helper');
const genericPool = require("generic-pool");
const os = require('os');
const Config = require('../../config');
const PuppeteerUtil = require('./util');

const createPuppeteerPool = function (opts) {
    let launchOption = {
        headless: "new",
      //  headless: false,
        dumpio: true,
        ignoreHTTPSErrors: true,
        defaultViewport: {
            width: 1280,
            height: 960
        },
        args: [
            '--headless',
            '--no-sandbox',
            '--disable-setuid-sandbox',
            '--disable-gpu',
            '--unlimited-storage',
            '--disable-dev-shm-usage',
            '--full-memory-crash-report',
            '--disable-extensions',
            '--mute-audio',
            '--no-zygote',
            '--no-first-run',
            '--start-maximized'
        ]
    };
    if(os.arch() === 'arm64'){
        launchOption.args.push('--single-process');
    }
    
    if(Config.puppeteerExecutablePath){
        launchOption.executablePath = Config.puppeteerExecutablePath;
    }
    
    let puppeteerFactory = {
        create: function () {
            helper.info("start one puppeteer instance");
            try{
                return puppeteer.launch(launchOption);
            }catch (e){
                helper.error("launch browser fail:" + e.toString());
                throw e;
            }
        },
        destroy: function (browser) {
            helper.info("destroy one puppeteer instance");
            try {
                browser.close();
            } catch (e) {
                helper.error("close browser fail:" + e.toString())
            }
        }
    };

    return genericPool.createPool(puppeteerFactory, opts);
};

const startBrowser = function (headless = 'new'){
    let launchOption = {
        headless: headless,
        dumpio: true,
        ignoreHTTPSErrors: true,
        defaultViewport: {
            width: 1280,
            height: 960
        },
        args: [
            '--no-sandbox',
            '--disable-setuid-sandbox',
            '--disable-gpu',
            '--unlimited-storage',
            '--disable-dev-shm-usage',
            '--full-memory-crash-report',
            '--disable-extensions',
            '--mute-audio',
            '--no-zygote',
            '--no-first-run',
            '--start-maximized'
        ]
    };
    if(os.arch() === 'arm64'){
        launchOption.args.push('--single-process');
    }

    if(Config.puppeteerExecutablePath){
        launchOption.executablePath = Config.puppeteerExecutablePath;
    }
    
    return puppeteer.launch(launchOption);
}

/**
 * 获取新页面实例
 *
 * @param doFunc
 * @param {width:int,height:int,timeout:int,?pageUrl:String,?html:String} options
 * @returns {Promise<Page>}
 */
const getPage = async function (options,doFunc, ) {
    // 页面打开后，关闭超时时间
    let timeout = ~~options.timeout || 30000;
    // 页面窗口宽高
    let width = ~~options.width;
    let height = ~~options.height;
    // 打开页面的URL
    
    //timeout = 5000;
    if (timeout <= 0) {
        timeout = 30000;
    }
    if (timeout < 2000) timeout = 2000;
    if (timeout > 300000) timeout = 300000;
    options.timeout = timeout;
    
    if (width > 10000) width = 10000;
    if (width < 0) width = 0;
    if (height > 10000) height = 10000;
    if (height < 0) height = 0;

    options.width = width;
    options.height = height;
    
    doFunc = doFunc || options.ready;
    if (typeof doFunc !== 'function') {
        throw "invalid ready callback param";
    }

    let browser, page;

    const releasePage = async function (cPage){
        if (!cPage) {
            return;
        }
        
        let url = cPage.url();
        if(url === 'about:blank'){
            return;
        }
        helper.log("close page:" + cPage.url());
        try {
            await cPage.close();
        } catch (e) {
            helper.error("close page err:" + e);
        }
    }
    
    const releaseBowser = async function () {
        if(!browser){
            return;
        }
        let thisBrowser = browser;
        browser = null;
        page = null;
        
        await helper.wait(100)
        try {
            let pages = await thisBrowser.pages();
            for (let i = 0; i < pages.length; i++) {
                releasePage(pages[i])
            }
            await browserPool.release(thisBrowser);
        } catch (e) {
            helper.error("close browser err:" + e);
        }
    };

    await helper.runWithTimeout(
        timeout,
        "time out:" + timeout + "ms",
        async function () {
            helper.log("fetch browser from pool");
            browser = await browserPool.acquire();
            helper.log("create new page start");
            page = await browser.newPage();
            helper.log("init page props");
            if (width > 0 && height > 0) {
                helper.log("set viewport: width:" + width + ',height:' + height);
                page.setViewport({width: width, height: height});
            }

            helper.log("create new page end");
            await doFunc(page);
            await releaseBowser();
        }
    ).finally(function () {
        releaseBowser();
    }).catch(function (e){
        throw e;
    });
};


/**
 * 打开新页面，并等待加载页面加载完整
 *
 * @param {Object} options 打开页面选项参数
 *  pageUrl 打开页面的URL
 *  timeout 打开页面超时
 *  width 可视区宽度
 *  height 可视区高度
 *  checkPageCompleteJs 检查页面加载完整,代码段
 *  delay 加载完整后延迟时间
 * @param {Function} doFunc
 *
 * @returns {Promise<Page>}
 */
const loadPage = async function (options, doFunc) {
    // 页面加载完成后延迟时间
    // 检查PDF实付完成的JS表达式，定时检测直到表达式值为true,是看上渲染
    let checkPageCompleteJs = options.checkPageCompleteJs;
    doFunc = doFunc || options.ready;
    if (typeof doFunc !== 'function') {
        throw "invalid ready callback param";
    }
    
    let pageUrl = (options.pageUrl || '') + '';
    if (pageUrl === '') {
        if (!options.html) {
            throw "pageUrl / html param can not both empty"
        }
    } else {
        if (!/^(https?|data):/.test(pageUrl)) {
            throw "invalid pageUrl param";
        }
    }
    
    await getPage(options,async function (page) {
        let timeout = options.timeout;
        let delay = ~~options.delay || 0;
        if (delay > timeout - 1000) delay = timeout - 1000;
        if (delay < 0) delay = 0;
        
        let pageUrl = options.pageUrl;
        helper.log("resolve page url");
        if (pageUrl === '') {
            pageUrl = await page.evaluate(htmlContent => {
                return URL.createObjectURL(new Blob([htmlContent], {
                    type: 'text/html'
                }));
            }, options.html);

            helper.log("html text to blob url:" + pageUrl)
        }
        else {
            // dataURL 浏览器有URL长度限制
            if (pageUrl.substr(0, 5) === 'data:') {
                pageUrl = await page.evaluate(dataUrl => {
                    let arr = dataUrl.split(',');
                    let mime = arr[0].match(/:(.*?);/)[1];
                    let bstr = decodeURIComponent(escape(atob(arr[1])));
                    return URL.createObjectURL(new Blob([bstr], {
                        type: mime
                    }));
                }, pageUrl);

                helper.log("data url to blob url:" + pageUrl)
            }
        }
        options.pageUrl = pageUrl;

        helper.log("open url:" + pageUrl);
        await page.goto(pageUrl,{timeout: timeout + 3000});
        helper.log("wait page load complete ...");
        await PuppeteerUtil.waitPageComplete(page, timeout, checkPageCompleteJs);
        helper.log("print delay:" + delay);
        await helper.wait(delay);
        helper.log("do user action");
        await doFunc(page);
    });
};


const makePdf = async function(options){
    let pdfPathInfo = helper.makePdfFileInfo();
    let ret = {
        metaInfo: null,
        pathInfo: null,
    };
    await loadPage({
        pageUrl: options.pageUrl,
        html: options.html,
        timeout: ~~options.timeout,
        delay: ~~options.delay,
        checkPageCompleteJs: options.checkPageCompleteJs,
    },  async function (page) {
        
        let metaInfo = await PuppeteerUtil.normalizeMetaInfo(options,page);
        await PuppeteerUtil.renderPdf(page, pdfPathInfo.fullPath,options.timeout);

        ret.metaInfo = metaInfo;
        ret.pathInfo = pdfPathInfo;
    });
    
    return ret;
};
/**
 * 初始化浏览器
 */
const initBrowserPool = function (maxProcess) {
    let pdfPath = helper.getPublicPath('pdf');
    if (!fs.existsSync(pdfPath)) {
        fs.mkdirSync(pdfPath);
    }

    if (maxProcess === undefined) {
        maxProcess = Config.maxBrowser;
    }
    if (maxProcess < 1) maxProcess = 1;
    
    let maxBrowserTaskWaiting = Config.maxBrowserTaskWaiting;
    if (maxBrowserTaskWaiting < 0) maxBrowserTaskWaiting = 0;


    helper.info("MAX_BROWSER:" + maxProcess);
    return createPuppeteerPool({
        max: maxProcess,
        min: 1, // minimum size of the pool
        idleTimeoutMillis: 600000,
        softIdleTimeoutMillis: 300000,
        evictionRunIntervalMillis: 1000,
        maxWaitingClients: maxBrowserTaskWaiting,
    });
};

const browserPool = initBrowserPool();

module.exports = {
    makePdf,
    loadPage,
    renderPdf: PuppeteerUtil.renderPdf,
    screenshotDOMElement: PuppeteerUtil.screenshotDOMElement,
    screenshotDOMElements: PuppeteerUtil.screenshotDOMElements,
    hideWebDriverSpecific: PuppeteerUtil.hideWebDriverSpecific,
    startBrowser,
    getPage,
};
